﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ADDDF.Infrastructure
{
    public abstract class RepositoryBase<T>
        : IRepository, IRepository<T> where T : IAggregateRoot
    {
        #region Constructors

        protected RepositoryBase() 
        {
        }

        #endregion

        #region IRepository<T> Members

        public abstract T FindByKey(object key);

        public abstract IEnumerable<T> FindAll();

        public abstract void Add(T item);

        public abstract void Remove(T item);

        public abstract void Change(T item);

        /// <summary>
        /// Provide default implementation for IRepository<T>.Set. This function
        /// uses IRepository<T>.FindByKey to attempt to locate an existing entity with
        /// the same key as the given item. It calls IRepository<T>.Change if an existing
        /// entity is found, and calls IRepository<T>.Add if an existing entity is not
        /// found. Derived class can override this to provide more efficient implementation.
        /// </summary>
        public virtual bool Set(T item)
        {
            if (FindByKey(item.Key) == null)
            {
                Add(item);
                return true;
            }
            else
            {
                Change(item);
                return false;
            }
        }

        #endregion

        #region SetEntityKey Helper Method For Derived Classes

        /// <summary>
        /// Set the key of an entity. Derived class can use this to assign
        /// database generated key to an entity.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="key"></param>
        protected void SetEntityKey(IEntity entity, object key)
        {
            IAllowKeyAssignment keyAssignable = entity as IAllowKeyAssignment;

            if (keyAssignable == null)
                throw new ArgumentException("Entity needs to implmement IAllowKeyAssignment to get key assigned");

            keyAssignable.AssignKey(key);
        }

        #endregion

        #region IUnitOfWorkRepository

        public void PersistNewItem(IEntity item)
        {
            IAggregateRoot aggregateRoot = item as IAggregateRoot;

            if (aggregateRoot == null)
                throw new ArgumentException("Item must implement IAggregateRoot");

            Add((T) aggregateRoot);
        }

        public void PersistUpdatedItem(IEntity item)
        {
            IAggregateRoot aggregateRoot = item as IAggregateRoot;

            if (aggregateRoot == null)
                throw new ArgumentException("Item must implement IAggregateRoot");

            Change((T) aggregateRoot);
        }

        public void PersistDeletedItem(IEntity item)
        {
            IAggregateRoot aggregateRoot = item as IAggregateRoot;

            if (aggregateRoot == null)
                throw new ArgumentException("Item must implement IAggregateRoot");

            Remove((T) aggregateRoot);
        }

        #endregion

        #region IRepository Implementation

        public void FindByKey(object key, out IAggregateRoot item)
        {
            item = FindByKey(key);
        }

        public void FindAll(out IEnumerable<IAggregateRoot> items)
        {
            items = FindAll().Select(x => (IAggregateRoot)x);
        }
        
        public void Add(IAggregateRoot item)
        {
            Add((T)item);
        }

        public void Change(IAggregateRoot item)
        {
            Change((T)item);
        }

        public void Remove(IAggregateRoot item)
        {
            Remove((T)item);
        }

        public bool Set(IAggregateRoot item)
        {
            return Set((T)item);
        }

        #endregion        
    }
}
